package com.huawei.codearts.service;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.huawei.codearts.model.Member;
import com.huawei.codearts.tools.utils.JsonUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.awt.*;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 参考地址：
 * demo地址:https://blog.csdn.net/anjiongyi/article/details/123391835
 * 官方demo地址：https://github.com/elastic/elasticsearch-java/blob/8.1/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/LoadingJsonTest.java
 */

@Slf4j
@Service
@RequiredArgsConstructor
public class EsApiService {

    private static final String index = "aidite_member_demo";
    private final ElasticsearchClient esClient;

    // demo地址：https://blog.csdn.net/qq_19283249/article/details/135352522
//    private static ElasticsearchClient initClient(){
//        String hostname = "127.0.0.1";
//        int port = 9200;
//        String username = "";
//        String password = "";
//        // 基本的用户名密码认证
//        BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider();
//        basicCredentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
//        RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost(hostname, port, "http"));
//        restClientBuilder.setHttpClientConfigCallback(httpAsyncClientBuilder ->
//                httpAsyncClientBuilder.setDefaultCredentialsProvider(basicCredentialsProvider));
//        RestClient restClient = restClientBuilder.build();
//        ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
//        ElasticsearchClient esClient = new ElasticsearchClient(transport);
//        return esClient;
//    }

    public Member getCreateData(Map<String, Object> params) {
        Member member = this.getData(params);
        long timestamp = System.currentTimeMillis();
        member.setCreatetime(timestamp);
        Date date = new Date(timestamp);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formattedDate = sdf.format(date);
        member.setCreatetimeFormat(formattedDate);
        SimpleDateFormat day = new SimpleDateFormat("yyyy-MM-dd");
        String formattedDay = day.format(date);
        member.setDay(formattedDay);
        return member;
    }

    public Member getUpdateData(Map<String, Object> params) {
        Member member = this.getData(params);
        return member;
    }

    public Member getData(Map<String, Object> params) {
        String id = (String)params.get("id");
        String user = (String)params.get("user");
        String name = (String)params.get("name");
        String status = (String)params.get("status");
        String money = (String)params.get("money");
        String country = (String)params.get("country");
        String description = (String)params.get("description");
        Member member = new Member();
        member.setId(Integer.parseInt(id));
        member.setUser(user);
        member.setName(name);
        member.setCountry(country);
        member.setDescription(description);
        member.setStatus(Integer.parseInt(status));
        member.setMoney(BigDecimal.valueOf(Double.parseDouble(money)));
        return member;
    }

    public Object create(Member member) throws IOException {
//        ElasticsearchClient esClient = initClient();
        String id = String.valueOf(member.getId());
        System.out.println("create member：" + member);
        CreateResponse createResponse = esClient.create(createRequest -> createRequest.index(index).id(id).document(member));
        System.out.println("添加索引请求结果：" + createResponse.result());  // created
        return createResponse.result().toString();
    }

    public Object update(Member member) throws IOException {
//        ElasticsearchClient esClient = initClient();
        String id = String.valueOf(member.getId());

//        Member mOld = (Member) this.findDataQueryOne("id", id);
//        member.setCreatetime(mOld.getCreatetime());
//        member.setCreatetimeFormat(mOld.getCreatetimeFormat());

        ObjectNode mOld = this.getId(id);
        member.setCreatetime(mOld.get("createtime").asLong());
        member.setCreatetimeFormat(mOld.get("createtimeFormat").asText());
        member.setDay(mOld.get("day").asText());

        System.out.println("update member：" + member);
        UpdateResponse<Member> updateResponse = esClient.update(updateRequest -> updateRequest.index(index).id(id).doc(member), Member.class);
        System.out.println("根据 ID 修改：" + updateResponse.result());  // Updated
        return updateResponse.result();
    }

    public Object delDataVidId(String id) throws IOException {
//        ElasticsearchClient esClient = initClient();
        DeleteResponse deleteResponse = esClient.delete(deleteRequest -> deleteRequest.index(index).id(id));
        System.out.println("按照 ID 删除索引数据结果：" + deleteResponse.result());  // deleted
        return deleteResponse.result().toString();
    }

    public Object delDataVidQuery(String value) throws IOException {
//        ElasticsearchClient esClient = initClient();
        DeleteByQueryResponse deleteByQueryResponse = esClient.deleteByQuery(deleteByQuery ->
                deleteByQuery.index(index).query(query ->
                        query.match(MatchQuery.of(builder ->
                                builder.field("name").query(value)))));
        System.out.println("按照条件删除索引数据结果：" + deleteByQueryResponse.deleted());
        return deleteByQueryResponse.deleted();
    }

    public ObjectNode getId(String id) throws IOException {
//        ElasticsearchClient esClient = initClient();

        // 使用实体方式映射数据
        GetResponse<Member> response = esClient.get(g -> g.index(index).id(id), Member.class);
        System.out.println("getId response 数据结果：" + response);
        Member item = response.source();
        System.out.println("getId item 数据结果：" + item);
//        return item;

        // ObjectNode 模式 如果实体的字段和es的字段不匹配可以用这种方式
        GetResponse<ObjectNode> responseJson = esClient.get(g -> g.index(index).id(id), ObjectNode.class);
        System.out.println("getId responseJson 数据结果：" + responseJson);
        ObjectNode json = responseJson.source();
        System.out.println("getId ObjectNode item 数据结果：" + json);
        String name = json.get("name").asText();
        System.out.println("getId ObjectNode name 数据结果：" + name);
        return json;
    }

    public Object findDataQueryOne(String key, String name) throws IOException {
//        ElasticsearchClient esClient = initClient();
        SearchResponse<Member> searchResponse = esClient.search(searchRequest ->
                searchRequest.index(index).query(q -> q.match(MatchQuery.of(builder ->
                        builder.field(key).query(name)))), Member.class);
        System.out.println("search searchResponse 数据结果：" + searchResponse);
        Hit<Member> memberInfo = searchResponse.hits().hits().getFirst();
        System.out.println("search memberInfo 数据结果：" + memberInfo.source());
        return memberInfo.source();
    }

    /**
     * 高级搜索
     */
    public Object search(String name) throws IOException {
//        ElasticsearchClient esClient = initClient();

        // 简单搜索查询
        SearchResponse<ObjectNode> searchResponse = esClient.search(searchRequest ->
                searchRequest.index(index).query(q -> q.match(MatchQuery.of(builder -> builder.field("name").query(name)))), ObjectNode.class);

        System.out.println("search searchResponse 数据结果：" + searchResponse);
        List<Hit<ObjectNode>> memberList = searchResponse.hits().hits();
        System.out.println("search memberList 数据结果：" + memberList);
//        searchResponse.hits().hits().forEach(memberInfoHit -> System.out.println(memberInfoHit))
        searchResponse.hits().hits().forEach(System.out::println);

        // 嵌套搜索查询
        double maxPrice = 616.6;
        Query byCountry = MatchQuery.of(m -> m
                .field("name")
                .query(name)
        )._toQuery();
        Query byMaxPrice = RangeQuery.of(r -> r
                .field("money")
                .gte(JsonData.of(maxPrice))
        )._toQuery();
        SearchResponse<ObjectNode> response = esClient.search(s -> s
                        .index(index)
                        .query(q -> q
                                .bool(b -> b
                                        .must(byCountry)
                                        .must(byMaxPrice)
                                )
                        ),
                ObjectNode.class
        );
        System.out.println("search response 数据结果：" + response);

        // 模版搜索查询
        esClient.putScript(r -> r
                .id("query-script")
                .script(s -> s
                        .lang("mustache")
                        .source("""
                                {
                                    "query":{
                                        "match":{
                                            "{{field}}":"{{value}}"
                                        }
                                    }
                                }
                                """)
                ));
        SearchTemplateResponse<ObjectNode> responseScript = esClient.searchTemplate(r -> r
                        .index(index)
                        .id("query-script")
                        .params("field", JsonData.of("name"))
                        .params("value", JsonData.of(name)),
                ObjectNode.class
        );
        System.out.println("search responseScript 数据结果：" + responseScript);

//        List<Member> dataList = new ArrayList<>();
        List<ObjectNode> dataList = new ArrayList<>();
        for (Hit<ObjectNode> hits : responseScript.hits().hits()) {
            ObjectNode item = hits.source();
            Member member = new Member();
            if (item != null) {
//                if (item.has("id")) {
//                    member.setId(item.get("id").asInt());
//                }
//                if (item.has("user")) {
//                    member.setUser(item.get("user").asText());
//                }
//                if (item.has("name")) {
//                    member.setName(item.get("name").asText());
//                }
//                if (item.has("status")) {
//                    member.setStatus(item.get("status").asInt());
//                }
//                if (item.has("money")) {
//                    member.setMoney(BigDecimal.valueOf(item.get("money").asDouble()));
//                }
//                if (item.has("country")) {
//                    member.setCountry(item.get("country").asText());
//                }
//                if (item.has("description")) {
//                    member.setDescription(item.get("description").asText());
//                }
//                if (item.has("timestamp")) {
//                    member.setCreatetime(item.get("timestamp").asLong());
//                }
//                if (item.has("createtime")) {
//                    member.setCreatetimeFormat(item.get("createtime").asText());
//                }
//                dataList.add(member);
                dataList.add(item);
            }
        }

        return dataList;
    }

    /**
     * 聚合查询
     */
    public Object aggregations() throws IOException {
//        ElasticsearchClient esClient = initClient();

        // 分组聚合查询
        Query query = MatchQuery.of(m -> m
                .field("description")
                .query("机构")
        )._toQuery();
        SearchResponse<Void> responseAgge = esClient.search(b -> b
                        .index(index)
                        .size(0)
                        .query(query)
                        .aggregations("country_histogram", a -> a
                                .terms(h -> h  // terms 分组
                                        .field("country")
                                ).aggregations("date_histogram", a2 -> a2
                                        .dateHistogram(h -> h  // 时间按月分组
                                                .field("createtime")
                                                .calendarInterval(CalendarInterval.Month)
                                                .format("yyyy-MM-dd")
                                        ).aggregations("count_histogram", a3 -> a3
                                                . cardinality(h -> h  // 去重总数
                                                        .field("user")
                                                        .precisionThreshold(400000)
                                                )
                                        )

                                )
                        ),
                Void.class
        );
        List<Map<String, Object>> itemsList = new ArrayList<>();
        System.out.println("search responseAgge 数据结果：" + responseAgge);
        Buckets<StringTermsBucket> aggeBuckets = responseAgge.aggregations().get("country_histogram").sterms().buckets();
        System.out.println("search country_histogram array 数据结果：" + aggeBuckets.array());
        for (StringTermsBucket bucket : aggeBuckets.array()) {
            System.out.println("bucket.key().stringValue():" + bucket.key().stringValue() + " = bucket.docCount():" + bucket.docCount());
            List<DateHistogramBucket> dateHistogram = bucket.aggregations().get("date_histogram").dateHistogram().buckets().array();
            System.out.println("search date_histogram array...：" + dateHistogram);

            Map<String, Object> dateItems = new ConcurrentHashMap<>();
            for (DateHistogramBucket dateBuck : dateHistogram) {
                System.out.println("search date_histogram... dateBuck.keyAsString():" + dateBuck.keyAsString() + " = dateBuck.docCount():" + dateBuck.docCount());
                long countHistogram = dateBuck.aggregations().get("count_histogram").cardinality().value();
                System.out.println("search count_histogram value...：" + countHistogram);

                dateItems.put("date", dateBuck.keyAsString());
                dateItems.put("count", countHistogram);
            }

            Map<String, Object> bItems = new ConcurrentHashMap<>();
            bItems.put("name", bucket.key().stringValue());
            bItems.put("data", dateItems);
            itemsList.add(bItems);
        }

        return itemsList;
    }

    /**
     * 聚合模版查询
     */
    public Object aggtpl() throws IOException {
//        ElasticsearchClient esClient = initClient();

        // 模版聚合查询
        esClient.putScript(r -> r
                .id("agge-script")
                .script(s -> s
                        .lang("mustache")
                        .source("""
                                {
                                    "query":{
                                        "match":{
                                            "{{field}}":"{{value}}"
                                        }
                                    },
                                    "size": 0,
                                    "aggregations": {
                                        "country_histogram": {
                                          "terms": {
                                            "field": "country"
                                          },
                                          "aggregations": {
                                            "date_histogram": {
                                              "date_histogram": {
                                                "field": "createtime",
                                                "interval": "month",
                                                "format": "yyyy-MM-dd"
                                              },
                                                "aggregations": {
                                                    "count_histogram": {
                                                      "cardinality": {
                                                        "field": "user",
                                                        "precision_threshold": 400000
                                                      }
                                                    }
                                                  }
                                            }
                                          }
                                        }
                                      }
                                }
                                """)
                ));
        SearchTemplateResponse<ObjectNode> responseScriptAgge = esClient.searchTemplate(r -> r
                        .index(index)
                        .id("agge-script")
                        .params("field", JsonData.of("description"))
                        .params("value", JsonData.of("机构")),
                ObjectNode.class
        );
        System.out.println("search responseScriptAgge 数据结果：" + responseScriptAgge);

        List<StringTermsBucket> buckets = responseScriptAgge.aggregations()
                .get("country_histogram")
                .sterms()
                .buckets().array();
        System.out.println("search aggregations.array 数据结果：" + buckets);

        List<Map<String, Object>> itemsList = new ArrayList<>();
        for (StringTermsBucket bucket: buckets) {
            System.out.println("bucket.key().stringValue():" + bucket.key().stringValue() + " = bucket.docCount():" + bucket.docCount());
            List<DateHistogramBucket> dateHistogram = bucket.aggregations().get("date_histogram").dateHistogram().buckets().array();
            System.out.println("search date_histogram array...：" + dateHistogram);

            Map<String, Object> dateItems = new ConcurrentHashMap<>();
            for (DateHistogramBucket dateBuck : dateHistogram) {
                System.out.println("search date_histogram... dateBuck.keyAsString():" + dateBuck.keyAsString() + " = dateBuck.docCount():" + dateBuck.docCount());
                long countHistogram = dateBuck.aggregations().get("count_histogram").cardinality().value();
                System.out.println("search count_histogram value...：" + countHistogram);

                dateItems.put("date", dateBuck.keyAsString());
                dateItems.put("count", countHistogram);
            }

            Map<String, Object> bItems = new ConcurrentHashMap<>();
            bItems.put("name", bucket.key().stringValue());
            bItems.put("data", dateItems);
            itemsList.add(bItems);
        }

        return itemsList;
    }

    /**
     * 聚合模版查询 json版
     */
    public Object aggjson() throws IOException {
//        ElasticsearchClient esClient = initClient();

        //tag::query-and-agg
        // now-1w: 过去一周的数据
        Reader queryJson = new StringReader("""
                {
                  "query": {
                    "range": {
                      "createtime": {
                        "gt": "now-1w"
                      }
                    }
                  },
                  "size": 100
                }
        """);

        Reader aggregationJson = new StringReader("""
                {
                  "size": 0,
                  "aggregations": {
                    "hours": {
                      "date_histogram": {
                        "field": "createtime",
                        "interval": "hour"
                      },
                      "aggregations": {
                        "max-cpu": {
                          "max": {
                            "field": "host.cpu.usage"
                          }
                        }
                      }
                    }
                  }
                }
        """);
        SearchRequest aggRequest = SearchRequest.of(b -> b
                .withJson(queryJson)
                .withJson(aggregationJson)
                .ignoreUnavailable(true)
        );
        Map<String, Aggregate> aggs = esClient
                .search(aggRequest, Void.class)
                .aggregations();

        System.out.println("search aggs 数据结果：" + aggs);

        List<DateHistogramBucket> buckets = aggs.get("hours")
                .dateHistogram()
                .buckets().array();
        System.out.println("search hours.array 数据结果：" + buckets);

        List<Map<String, Object>> itemsList = new ArrayList<>();
        for (DateHistogramBucket bucket: buckets) {
            System.out.println("bucket.keyAsString():" + bucket.keyAsString() + " = bucket.docCount():" + bucket.docCount());
            MaxAggregate maxHistogram = bucket.aggregations().get("max-cpu").max();
            System.out.println("search max-cpu array...：" + maxHistogram);
            Map<String, Object> bItems = new ConcurrentHashMap<>();
            bItems.put("name", bucket.keyAsString());
            bItems.put("max_cup", maxHistogram.value());
            itemsList.add(bItems);
        }

        return itemsList;
    }

}